﻿using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using Pumpkin.Utils;

namespace Pumpkin.FrameBase.Resource
{
    public class BundleManager : Singleton<BundleManager>
    {
        private HashSet<string> CommonBundles = new HashSet<string>();
        private Dictionary<string, HashSet<string>> SceneBundles = new Dictionary<string, HashSet<string>>();
        private Dictionary<string, AssetBundle> bundles = new Dictionary<string, AssetBundle>();
        private Dictionary<string, string> bundleFileMap = new Dictionary<string, string>();
        private Dictionary<string, Dictionary<string, string>> ext2name2path;
        private AssetBundle mainBundle;
        private AssetBundleManifest mainManifest;
        private string bundlePath;
        private bool isInit = false;

        public BundleManager()
        {
            bundlePath = FileHelper.CombinePath(FileHelper.GetPersisitentPath(), HotFixConfigs.BundleRootDir);
        }

        public void Init()
        {
            if(isInit)
            {
                return;
            }

            mainBundle = AssetBundle.LoadFromFile(FileHelper.CombinePath(bundlePath, HotFixConfigs.BundleRootDir));
            if (mainBundle == null)
            {
                Log.LogError("main bundle can not be loaded");
                return;
            }

            mainManifest = mainBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            if (mainManifest == null)
            {
                Log.LogError("main bundle's manifest can not be loaded");
                return;
            }

            var mapPath = FileHelper.CombinePath(FileHelper.GetPersisitentPath(), HotFixConfigs.BundleFileTable);
            var mapData = FileHelper.FileReadAllText(mapPath);
            if (mapData == null || mapData.Length == 0)
            {
                Log.LogWarning("no bundle files");
                return;
            }
            bundleFileMap = LitJson.JsonMapper.ToObject<Dictionary<string, string>>(mapData);

            if (HotFixConfigs.EnableResLoadByName)
            {
                var ext2name2path_path = FileHelper.CombinePath(FileHelper.GetPersisitentPath(), HotFixConfigs.Ext2File2PathFile);
                var ext2name2path_text = FileHelper.FileReadAllText(ext2name2path_path);
                if (ext2name2path_text == null || ext2name2path_text.Length == 0)
                {
                    Log.LogWarning("cant load Ext2Name2Path File");
                    return;
                }
                ext2name2path = LitJson.JsonMapper.ToObject<Dictionary<string, Dictionary<string, string>>>(ext2name2path_text);
            }

            Log.LogInfo("Init Bundle finish", LogColor.Yellow);
            isInit = true;
        }

        public void SetCommonBundles(List<string> bundles)
        {
            foreach(var bundle in bundles)
            {
                CommonBundles.Add(bundle);
            }
        }

        public void SetSceneBundles(string scene, List<string> bundles)
        {
            HashSet<string> bundleSet = new HashSet<string>(); 
            foreach(var bundle in bundles)
            {
                bundleSet.Add(bundle);
            }
            SceneBundles.Add(scene, bundleSet);
        }

        public void LoadBundle(string bundleName)
        {
            AssetBundle bundle;
            if (bundles.TryGetValue(bundleName, out bundle))
            {
                return;
            }
            else
            {
                LoadDependencies(bundleName);
                bundle = AssetBundle.LoadFromFile(FileHelper.CombinePath(bundlePath, bundleName));
                if (bundle == null)
                {
                    Log.LogError(bundleName + ":asset bundle can not be loaded");
                    return;
                }
                bundles.Add(bundleName, bundle);
                return;
            }
        }

        public void LoadDependencies(string hostBundle)
        {
            string[] deps = mainManifest.GetAllDependencies(hostBundle);
            foreach (var dep in deps)
            {
                AssetBundle depBundle;
                bundles.TryGetValue(dep, out depBundle);
                if (depBundle == null)
                {
                    depBundle = AssetBundle.LoadFromFile(FileHelper.CombinePath(bundlePath, dep));
                    if (depBundle == null)
                    {
                        Log.LogWarning(hostBundle + "'s dependent bundle: " + dep + " cant be loaded");
                        continue;
                    }

                    bundles.Add(dep, depBundle);
                }
            }
        }

        public void UnloadBundle(string bundleName, bool force = false)
        {
            AssetBundle bundle;
            if (bundles.TryGetValue(bundleName, out bundle))
            {
                bundle.Unload(force);
                bundles.Remove(bundleName);
            }
            else
            {
                Log.LogWarning("Try to unload an not exist bundle:" + bundleName);
            }
        }

        public void UnloadBundle(AssetBundle bundle, bool force = false)
        {
            string bundleName = null;
            foreach (var item in bundles)
            {
                if (item.Value == bundle)
                {
                    bundleName = item.Key;
                }
            }

            if (bundleName != null)
            {
                UnloadBundle(bundleName, force);
            }
        }

        public void LoadSceneBundles(string scene)
        {
            HashSet<string> nxtBundleNames;
            SceneBundles.TryGetValue(scene, out nxtBundleNames);

            var bundleNames = bundles.Keys.ToArray();
            foreach (var bundleName in bundleNames)
            {
                if (CommonBundles.Contains(bundleName) || (nxtBundleNames != null && nxtBundleNames.Contains(bundleName)))
                {
                    continue;
                }
                UnloadBundle(bundleName, true);
            }

            if (nxtBundleNames != null)
            {
                foreach (var bundleName in nxtBundleNames)
                {
                    LoadBundle(bundleName);
                }
            }
        }

        public void LoadCommonBundles()
        {
            foreach (var bundleName in CommonBundles)
            {
                LoadBundle(bundleName);
            }
        }

        public string GetBundleNameByFile(string fileName)
        {
            string bundleName;
            if (!bundleFileMap.TryGetValue(fileName, out bundleName))
            {
                Log.LogError("cant find the bundle which contains file:" + fileName);
            }
            return bundleName;
        }

        public T LoadAssetFromBundle<T>(string fileName, bool autoUnload = true) where T : UnityEngine.Object
        {
            var bundleName = GetBundleNameByFile(fileName);
            if (bundleName == null)
            {
                return default(T);
            }

            AssetBundle bundle;
            if (bundles.TryGetValue(bundleName, out bundle))
            {
                return bundle.LoadAsset<T>(fileName);
            }
            else
            {
                LoadDependencies(bundleName);
                bundle = AssetBundle.LoadFromFile(FileHelper.CombinePath(bundlePath, bundleName));
                if (bundle == null)
                {
                    Log.LogError(bundleName + ":asset bundle can not be loaded");
                    return null;
                }

                var asset = bundle.LoadAsset<T>(fileName);
                if (autoUnload)
                {
                    bundle.Unload(false);
                }
                else
                {
                    bundles.Add(bundleName, bundle);
                }
                return asset;
            }
        }

        public string GetFilePathByName(string name, string ext)
        {
            Dictionary<string, string> name2path;
            if(!ext2name2path.TryGetValue(ext, out name2path))
            {
                return null;
            }

            string path;
            if(!name2path.TryGetValue(name, out path))
            {
                return null;
            }
            return path;
        }
    }
}

